/*
 * Copyright 2019 xiaomaoguai.com All right reserved. This software is the
 * confidential and proprietary information of xiaomaoguai.com ("Confidential
 * Information"). You shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license agreement you entered
 * into with xiaomaoguai.com.
 */

package com.xiaomaoguai.core.next.tddl.config;

import static app.myoss.cloud.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.createSqlSessionFactoryBean;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import javax.sql.DataSource;

import com.xiaomaoguai.core.next.tddl.sequence.TddlSequence;
import com.xiaomaoguai.core.next.tddl.sequence.TddlSequenceUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ClassUtils;

import com.taobao.tddl.client.jdbc.TDataSource;
import com.taobao.tddl.client.sequence.impl.GroupSequence;
import com.taobao.tddl.client.sequence.impl.GroupSequenceDao;

import app.myoss.cloud.core.exception.BizRuntimeException;
import app.myoss.cloud.mybatis.mapper.register.MapperInterfaceRegister;
import app.myoss.cloud.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import app.myoss.cloud.mybatis.spring.boot.autoconfigure.MybatisProperties;
import app.myoss.cloud.mybatis.spring.boot.autoconfigure.MybatisProperties.MapperScanner;
import app.myoss.cloud.mybatis.spring.mapper.MapperScannerConfigurer;
import app.myoss.cloud.mybatis.table.Sequence;
import app.myoss.cloud.mybatis.table.TableConfig;
import app.myoss.cloud.mybatis.table.TableInfo;
import app.myoss.cloud.mybatis.table.TableMetaObject;
import app.myoss.cloud.mybatis.table.TableSequence;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * TDDL 自动配置，支持多个数据库
 *
 * @author chenyao
 * @since 2018年7月16日 下午4:22:42
 */
@Slf4j
public class TddlMultiScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean,
        ApplicationContextAware, BeanNameAware, ApplicationListener<ApplicationReadyEvent>, ResourceLoaderAware {
    /**
     * DataSource Bean 名称的后缀名
     */
    public static final String  DATA_SOURCE_BEAN_NAME_SUFFIX               = "DataSource";
    /**
     * DataSourceTransactionManager Bean 名称的后缀名
     */
    public static final String  TRANSACTION_MANAGER_BEAN_NAME_SUFFIX       = "TransactionManager";
    /**
     * TransactionTemplate Bean 名称的后缀名
     */
    public static final String  TRANSACTION_TEMPLATE_BEAN_NAME_SUFFIX      = "TransactionTemplate";
    /**
     * GroupSequenceDao Bean 名称的后缀名
     */
    public static final String  GROUP_SEQUENCE_DAO_BEAN_NAME_SUFFIX        = "GroupSequenceDao";
    /**
     * GroupSequenceDao Bean 名称的key
     */
    public static final String  GROUP_SEQUENCE_DAO_BEAN_NAME_KEY           = "groupSequenceDaoBeanName";
    /**
     * SqlSessionFactory Bean 名称的后缀名
     */
    public static final String  SQL_SESSION_FACTORY_BEAN_NAME_SUFFIX       = "SqlSessionFactory";
    /**
     * MapperScannerConfigurer Bean 名称的后缀名
     */
    public static final String  MAPPER_SCANNER_CONFIGURER_BEAN_NAME_SUFFIX = "MapperScannerConfigurer";

    @Setter
    private TddlMultiProperties properties;
    @Setter
    private ApplicationContext  applicationContext;
    /**
     * resource loader
     */
    @Setter
    private ResourceLoader      resourceLoader;
    private boolean             mybatisIsPresent;
    private List<String>        groupSequenceDaoBeanNames                  = new ArrayList<>();

    @Override
    public void setBeanName(String name) {
        log.info("init bean {}", name);
    }

    @Override
    public void afterPropertiesSet() {
        Objects.requireNonNull(this.properties);
        Objects.requireNonNull(this.applicationContext);
        Objects.requireNonNull(this.resourceLoader);
        this.mybatisIsPresent = ClassUtils.isPresent(
                "app.myoss.cloud.mybatis.spring.boot.autoconfigure.MybatisProperties",
                this.getClass().getClassLoader());
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        List<TddlMultiProperties.TddlProperty> databases = properties.getDatabases();
        for (TddlMultiProperties.TddlProperty item : databases) {
            registerTddlRelatedBean(registry, item);
        }
    }

    /**
     * 注册 TDDL 相关联的 Bean
     *
     * @param registry Bean注册器
     * @param item TDDL 属性配置
     */
    public void registerTddlRelatedBean(BeanDefinitionRegistry registry, TddlMultiProperties.TddlProperty item) {
        // 注册数据源bean
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(TDataSource.class, () -> dataSource(item))
                .getRawBeanDefinition();
        if (item.isPrimary()) {
            beanDefinition.setPrimary(true);
        }
        beanDefinition.setInitMethodName("init");
        String beanNamePrefix = StringUtils.defaultIfBlank(item.getBeanNamePrefix(), item.getAppName());
        String dataSourceBeanName = beanNamePrefix + DATA_SOURCE_BEAN_NAME_SUFFIX;
        registry.registerBeanDefinition(dataSourceBeanName, beanDefinition);
        TDataSource dataSource = this.applicationContext.getBean(dataSourceBeanName, TDataSource.class);

        // 注册事务管理bean
        beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(DataSourceTransactionManager.class, () -> transactionManager(dataSource))
                .getRawBeanDefinition();
        if (item.isPrimary()) {
            beanDefinition.setPrimary(true);
        }
        String transManageBeanName = beanNamePrefix + TRANSACTION_MANAGER_BEAN_NAME_SUFFIX;
        registry.registerBeanDefinition(transManageBeanName, beanDefinition);
        DataSourceTransactionManager transactionManager = this.applicationContext.getBean(transManageBeanName,
                DataSourceTransactionManager.class);

        // 注册事务管理模版bean
        beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(TransactionTemplate.class, () -> transactionTemplate(transactionManager))
                .getRawBeanDefinition();
        if (item.isPrimary()) {
            beanDefinition.setPrimary(true);
        }
        String transTemplateBeanName = beanNamePrefix + TRANSACTION_TEMPLATE_BEAN_NAME_SUFFIX;
        registry.registerBeanDefinition(transTemplateBeanName, beanDefinition);

        // 注册TDDL GroupSequenceDao bean
        beanDefinition = BeanDefinitionBuilder
                .genericBeanDefinition(GroupSequenceDao.class, () -> TddlSequenceUtils.buildSequenceDao(item, false))
                .getRawBeanDefinition();
        if (item.isPrimary()) {
            beanDefinition.setPrimary(true);
        }
        beanDefinition.setInitMethodName("init");
        String groupSequenceDaoBeanName = beanNamePrefix + GROUP_SEQUENCE_DAO_BEAN_NAME_SUFFIX;
        registry.registerBeanDefinition(groupSequenceDaoBeanName, beanDefinition);
        groupSequenceDaoBeanNames.add(groupSequenceDaoBeanName);

        MybatisProperties mybatis = item.getMybatis();
        // 初始化表的 TDDL sequence，用于生成主键id
        if (mybatis != null && mybatisIsPresent) {
            // 注册 SqlSessionFactory bean
            beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class, () -> {
                Map<String, Interceptor> interceptorMap = this.applicationContext.getBeansOfType(Interceptor.class);
                Interceptor[] interceptors = interceptorMap.values().toArray(new Interceptor[0]);
                Map<String, ConfigurationCustomizer> configurationCustomizerMap = this.applicationContext
                        .getBeansOfType(ConfigurationCustomizer.class);
                List<ConfigurationCustomizer> configurationCustomizers = new ArrayList<>(
                        configurationCustomizerMap.values());
                return createSqlSessionFactoryBean(this.applicationContext, this.resourceLoader, dataSource, mybatis,
                        configurationCustomizers, interceptors);
            }).getRawBeanDefinition();
            if (item.isPrimary()) {
                beanDefinition.setPrimary(true);
            }
            String sqlSessionBeanName = beanNamePrefix + SQL_SESSION_FACTORY_BEAN_NAME_SUFFIX;
            registry.registerBeanDefinition(sqlSessionBeanName, beanDefinition);

            // 注册 MapperScannerConfigurer bean
            Supplier<MapperScannerConfigurer> supplier = () -> mapperScannerConfigurer(mybatis,
                    groupSequenceDaoBeanName, sqlSessionBeanName);
            beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class, supplier)
                    .getRawBeanDefinition();
            String beanName = beanNamePrefix + MAPPER_SCANNER_CONFIGURER_BEAN_NAME_SUFFIX;
            registry.registerBeanDefinition(beanName, beanDefinition);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (!mybatisIsPresent) {
            return;
        }
        log.info("init TDDL sequence");
        Collection<Sequence> values = TableMetaObject.getSequenceBeanMap().values();
        for (Sequence value : values) {
            if (!(value instanceof TddlSequence)) {
                continue;
            }
            TddlSequence tddlSequence = (TddlSequence) value;
            if (tddlSequence.getSequence() != null) {
                continue;
            }
            TableInfo tableInfo = tddlSequence.getTableInfo();
            TableSequence tableSequence = tableInfo.getTableSequence();
            String sequenceName = tableSequence.getSequenceName();
            if (StringUtils.isBlank(sequenceName)) {
                throw new NullPointerException("sequenceName is null, tableInfo: " + tableInfo);
            }

            Map<Object, Object> customizeConfig = tableInfo.getTableConfig().getCustomizeConfig();
            if (customizeConfig != null && customizeConfig.containsKey(GROUP_SEQUENCE_DAO_BEAN_NAME_KEY)) {
                String groupSequenceDaoBeanName = (String) customizeConfig.get(GROUP_SEQUENCE_DAO_BEAN_NAME_KEY);
                for (String itemName : groupSequenceDaoBeanNames) {
                    if (!StringUtils.equals(itemName, groupSequenceDaoBeanName)) {
                        continue;
                    }
                    GroupSequence sequence = TddlSequenceUtils.initTddlSequence(this.applicationContext, sequenceName,
                            itemName, null);
                    if (sequence == null) {
                        throw new BizRuntimeException("create TDDL sequence failed, sequenceName = " + sequenceName
                                + ", tableName = " + tddlSequence.getTableInfo().getTableName());
                    }
                    tddlSequence.setSequence(sequence);
                    break;
                }
            }
        }
    }

    /**
     * 初始化 TDDL 数据源
     *
     * @param property TDDL 属性配置
     * @return TDDL 数据源
     */
    public static TDataSource dataSource(TddlMultiProperties.TddlProperty property) {
        TDataSource dataSource = new TDataSource();
        dataSource.setAppName(property.getAppName());
        dataSource.setDynamicRule(true);
        return dataSource;
    }

    /**
     * 初始化数据源事务管理者
     *
     * @param dataSource 数据源
     * @return 数据源事务管理者
     */
    public static DataSourceTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

    /**
     * 初始化事务管理模版
     *
     * @param transactionManager 数据源事务管理者
     * @return 事务管理模版
     */
    public static TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager) {
        TransactionTemplate template = new TransactionTemplate();
        template.setTransactionManager(transactionManager);
        return template;
    }

    /**
     * 初始化 MapperScannerConfigurer
     *
     * @param mybatis MyBatis配置属性
     * @param groupSequenceDaoBeanName GroupSequenceDao Bean 名称
     * @param sqlSessionFactoryBeanName SqlSessionFactory Bean 名称
     * @return MapperScannerConfigurer 实例对象
     */
    public static MapperScannerConfigurer mapperScannerConfigurer(MybatisProperties mybatis,
                                                                  String groupSequenceDaoBeanName,
                                                                  String sqlSessionFactoryBeanName) {
        TableConfig tableConfig = mybatis.getTableConfig();
        if (mybatis.getTableConfig() == null) {
            tableConfig = new TableConfig();
        }
        if (tableConfig.getCustomizeConfig() == null) {
            tableConfig.setCustomizeConfig(new HashMap<>(4));
        }
        Map<Object, Object> customizeConfig = tableConfig.getCustomizeConfig();
        customizeConfig.put(GROUP_SEQUENCE_DAO_BEAN_NAME_KEY, groupSequenceDaoBeanName);
        tableConfig.setCustomizeConfig(customizeConfig);
        MapperInterfaceRegister mapperInterfaceRegister = new MapperInterfaceRegister(tableConfig);

        MapperScanner scanner = mybatis.getMapperScanner();
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage(scanner.getBasePackage());
        mapperScannerConfigurer.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName);
        mapperScannerConfigurer.setSqlSessionTemplateBeanName(scanner.getSqlSessionTemplateBeanName());
        if (scanner.getAnnotationClass() != null) {
            mapperScannerConfigurer.setAnnotationClass(scanner.getAnnotationClass());
        }
        if (scanner.getMarkerInterface() != null) {
            mapperScannerConfigurer.setMarkerInterface(scanner.getMarkerInterface());
        }
        mapperScannerConfigurer.setMapperInterfaceRegister(mapperInterfaceRegister);
        return mapperScannerConfigurer;
    }
}
